package com.keter.framework.persist.base;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import com.jfinal.plugin.activerecord.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 *  缓存基类：提供以子类名称为缓存名的默认实现
 *  注：无法使用AOP方式动态创建缓存，因为使用代理无法正确获取子类名称
 * @author  gulixing@msn.com
 * @date    2015年12月29日
 */
public  abstract class CacheModel<M extends CacheModel> extends BaseModel<M> {
    /**
     * Logger for this class
     */
    private static final Logger logger = LoggerFactory.getLogger(CacheModel.class);

    private int expireSeconds = 60;
    private int size = 1000;

    static Map<String, LoadingCache> localCachePool = Maps.newHashMap();

    public void cacheConfig(int expire,int size){
        this.expireSeconds = expire;
        this.size = size;
    }

    @Override
    public M findById(Object id) {
        //把整型、长整型ID统一转换成字符串处理
        String key = String.valueOf(id);
        String cacheName = getCacheName();
        LoadingCache<String, Model> cacheObj = localCachePool.get(cacheName);
        if(cacheObj == null) {
            cacheObj = CacheBuilder.newBuilder()
                    .maximumSize(size)
                    .expireAfterWrite(expireSeconds,TimeUnit.SECONDS)
                    .build(new CacheLoader<String, Model>() {
                        @Override
                        public Model load(String key) {
                            // 这里是key根据实际去取值的方法
                            return findByIdFromDB(key);
                        }
                    });
            localCachePool.put(cacheName, cacheObj);
        }
        try {
            return (M) cacheObj.get(key);
        } catch (ExecutionException | CacheLoader.InvalidCacheLoadException e  ) {
            return super.findById(key);
        }
    }

    /**
     * 抽象基类不能以注解方式隐式创建缓存，主要原因在于无法预知子类的包、类限定名！
     * @param id
     * @return
     */
    private M findByIdFromDB(String id){
//        logger.info("query form DB...");
        return super.findById(id);
    }

    @Override
    public boolean update(){
        boolean result = super.update();
        if(null == this.getID()){
            logger.info("WARN: 记录主键（ID）为空，缓存无法更新或生效");
            return result;
        }
        refresh(getCacheName(),this.getID().toString());
        return result;
    }

    @Override
    public boolean delete() {
        boolean result =  super.delete();
        deleteCache(getCacheName(),getID().toString());
        return result;
    }

    private static void refresh(String cacheName, String cacheKey){
        LoadingCache cache = localCachePool.get(cacheName);
        if(null == cache){
            logger.info("本地缓存[{}]不存在，刷新失败！",cacheName);
            return;
        }
        cache.refresh(cacheKey);
    }

    private static void deleteCache(String cacheName, String cacheKey){
        LoadingCache cache = localCachePool.get(cacheName);
        if(null == cache){
            logger.info("本地缓存[{}]不存在!");
            return;
        }
        cache.invalidate(cacheKey);
    }


    /**
     * 设置缓存名称
     * @return
     * @author  gulixing@msn.com
     * @date    2015年12月29日
     */
    private String getCacheName(){
        return this.getClass().getCanonicalName();
    }
}
